﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

namespace K9Nano.Extensions;

/// <summary>
/// Defines helper methods for reflection.
/// </summary>
public static class ReflectionHelper
{
    /// <summary>
    /// Checks whether <paramref name="givenType"/> implements/inherits <paramref name="genericType"/>.
    /// </summary>
    /// <param name="givenType">Type to check</param>
    /// <param name="genericType">Generic type</param>
    public static bool IsAssignableToGenericType(Type givenType, Type genericType)
    {
        var givenTypeInfo = givenType.GetTypeInfo();

        if (givenTypeInfo.IsGenericType && givenType.GetGenericTypeDefinition() == genericType)
        {
            return true;
        }

        foreach (var interfaceType in givenType.GetInterfaces())
        {
            if (interfaceType.GetTypeInfo().IsGenericType && interfaceType.GetGenericTypeDefinition() == genericType)
            {
                return true;
            }
        }

        if (givenTypeInfo.BaseType == null)
        {
            return false;
        }

        return IsAssignableToGenericType(givenTypeInfo.BaseType, genericType);
    }

    /// <summary>
    /// Gets a list of attributes defined for a class member and it's declaring type including inherited attributes.
    /// </summary>
    /// <param name="inherit">Inherit attribute from base classes</param>
    /// <param name="memberInfo">MemberInfo</param>
    public static List<object> GetAttributesOfMemberAndDeclaringType(MemberInfo memberInfo, bool inherit = true)
    {
        var attributeList = new List<object>();

        attributeList.AddRange(memberInfo.GetCustomAttributes(inherit));

        if (memberInfo.DeclaringType != null)
        {
            attributeList.AddRange(memberInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(inherit));
        }

        return attributeList;
    }

    /// <summary>
    /// Gets a list of attributes defined for a class member and type including inherited attributes.
    /// </summary>
    /// <param name="memberInfo">MemberInfo</param>
    /// <param name="type">Type</param>
    /// <param name="inherit">Inherit attribute from base classes</param>
    public static List<object> GetAttributesOfMemberAndType(MemberInfo memberInfo, Type type, bool inherit = true)
    {
        var attributeList = new List<object>();
        attributeList.AddRange(memberInfo.GetCustomAttributes(inherit));
        attributeList.AddRange(type.GetTypeInfo().GetCustomAttributes(inherit));
        return attributeList;
    }

    /// <summary>
    /// Gets a list of attributes defined for a class member and it's declaring type including inherited attributes.
    /// </summary>
    /// <typeparam name="TAttribute">Type of the attribute</typeparam>
    /// <param name="memberInfo">MemberInfo</param>
    /// <param name="inherit">Inherit attribute from base classes</param>
    public static List<TAttribute> GetAttributesOfMemberAndDeclaringType<TAttribute>(MemberInfo memberInfo, bool inherit = true)
        where TAttribute : Attribute
    {
        var attributeList = new List<TAttribute>();

        if (memberInfo.IsDefined(typeof(TAttribute), inherit))
        {
            attributeList.AddRange(memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>());
        }

        if (memberInfo.DeclaringType != null && memberInfo.DeclaringType.GetTypeInfo().IsDefined(typeof(TAttribute), inherit))
        {
            attributeList.AddRange(memberInfo.DeclaringType.GetTypeInfo().GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>());
        }

        return attributeList;
    }

    /// <summary>
    /// Gets a list of attributes defined for a class member and type including inherited attributes.
    /// </summary>
    /// <typeparam name="TAttribute">Type of the attribute</typeparam>
    /// <param name="memberInfo">MemberInfo</param>
    /// <param name="type">Type</param>
    /// <param name="inherit">Inherit attribute from base classes</param>
    public static List<TAttribute> GetAttributesOfMemberAndType<TAttribute>(MemberInfo memberInfo, Type type, bool inherit = true)
        where TAttribute : Attribute
    {
        var attributeList = new List<TAttribute>();

        if (memberInfo.IsDefined(typeof(TAttribute), inherit))
        {
            attributeList.AddRange(memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>());
        }

        if (type.GetTypeInfo().IsDefined(typeof(TAttribute), inherit))
        {
            attributeList.AddRange(type.GetTypeInfo().GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>());
        }

        return attributeList;
    }

    /// <summary>
    /// Tries to gets an of attribute defined for a class member and it's declaring type including inherited attributes.
    /// Returns default value if it's not declared at all.
    /// </summary>
    /// <typeparam name="TAttribute">Type of the attribute</typeparam>
    /// <param name="memberInfo">MemberInfo</param>
    /// <param name="defaultValue">Default value (null as default)</param>
    /// <param name="inherit">Inherit attribute from base classes</param>
    public static TAttribute? GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute? defaultValue = default, bool inherit = true)
        where TAttribute : class
    {
        return memberInfo.GetCustomAttributes(inherit).OfType<TAttribute>().FirstOrDefault()
               ?? memberInfo.ReflectedType?.GetTypeInfo().GetCustomAttributes(true).OfType<TAttribute>().FirstOrDefault()
               ?? defaultValue;
    }

    /// <summary>
    /// Tries to gets an of attribute defined for a class member and it's declaring type including inherited attributes.
    /// Returns default value if it's not declared at all.
    /// </summary>
    /// <typeparam name="TAttribute">Type of the attribute</typeparam>
    /// <param name="memberInfo">MemberInfo</param>
    /// <param name="defaultValue">Default value (null as default)</param>
    /// <param name="inherit">Inherit attribute from base classes</param>
    public static TAttribute? GetSingleAttributeOrDefault<TAttribute>(MemberInfo memberInfo, TAttribute? defaultValue = default, bool inherit = true)
        where TAttribute : Attribute
    {
        //Get attribute on the member
        if (memberInfo.IsDefined(typeof(TAttribute), inherit))
        {
            return memberInfo.GetCustomAttributes(typeof(TAttribute), inherit).Cast<TAttribute>().First();
        }

        return defaultValue;
    }

    /// <summary>
    /// Gets a property by it's full path from given object
    /// </summary>
    /// <param name="obj">Object to get value from</param>
    /// <param name="objectType">Type of given object</param>
    /// <param name="propertyPath">Full path of property</param>
    /// <returns></returns>
    public static object GetPropertyByPath(object obj, Type objectType, string propertyPath)
    {
        var property = obj;
        var currentType = objectType;
        var objectPath = currentType.FullName;
        var absolutePropertyPath = propertyPath;
        if (!string.IsNullOrEmpty(objectPath) && absolutePropertyPath.StartsWith(objectPath))
        {
            absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", "");
        }

        foreach (var propertyName in absolutePropertyPath.Split('.'))
        {
            property = currentType.GetProperty(propertyName);
            currentType = ((PropertyInfo)property!).PropertyType;
        }

        return property;
    }

    /// <summary>
    /// Gets value of a property by it's full path from given object
    /// </summary>
    /// <param name="obj">Object to get value from</param>
    /// <param name="objectType">Type of given object</param>
    /// <param name="propertyPath">Full path of property</param>
    /// <returns></returns>
    public static object? GetValueByPath(object obj, Type objectType, string propertyPath)
    {
        var value = obj;
        var currentType = objectType;
        var objectPath = currentType.FullName;
        var absolutePropertyPath = propertyPath;
        if (!string.IsNullOrEmpty(objectPath) && absolutePropertyPath.StartsWith(objectPath))
        {
            absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", "");
        }

        foreach (var propertyName in absolutePropertyPath.Split('.'))
        {
            var property = currentType.GetProperty(propertyName);
            if(property is null) continue;
            value = property.GetValue(value, null);
            currentType = property.PropertyType;
        }

        return value;
    }

    /// <summary>
    /// Sets value of a property by it's full path on given object
    /// </summary>
    /// <param name="obj"></param>
    /// <param name="objectType"></param>
    /// <param name="propertyPath"></param>
    /// <param name="value"></param>
    public static void SetValueByPath(object obj, Type objectType, string propertyPath, object value)
    {
        var currentType = objectType;
        PropertyInfo? property;
        var objectPath = currentType.FullName;
        var absolutePropertyPath = propertyPath;
        if (!string.IsNullOrEmpty(objectPath) && absolutePropertyPath.StartsWith(objectPath))
        {
            absolutePropertyPath = absolutePropertyPath.Replace(objectPath + ".", "");
        }

        var properties = absolutePropertyPath.Split('.');

        if (properties.Length == 1)
        {
            property = objectType.GetProperty(properties.First());
            if (property is null) return;
            property.SetValue(obj, value);
            return;
        }

        for (var i = 0; i < properties.Length - 1; i++)
        {
            property = currentType.GetProperty(properties[i]);
            if (property is null) return;
            var objTemp = property.GetValue(obj, null);
            if (objTemp is null) return;
            obj = objTemp;
            currentType = property.PropertyType;
        }

        property = currentType.GetProperty(properties.Last());
        if (property is null) return;
        property.SetValue(obj, value);
    }

    public static bool IsPropertyGetterSetterMethod(MethodInfo method, Type type)
    {
        if (!method.IsSpecialName)
        {
            return false;
        }

        if (method.Name.Length < 5)
        {
            return false;
        }

        return type.GetProperty(method.Name[4..], BindingFlags.Instance | BindingFlags.Static | BindingFlags.NonPublic) != null;
    }

    public static void SetValue(object obj, PropertyInfo property, object? value)
    {
        object? propertyValue;
        if (value == null)
        {
            propertyValue = default;
        }
        else
        {
            if (property.PropertyType.IsEnum)
            {
                propertyValue = Enum.Parse(property.PropertyType, value.ToString()!);
            }
            else if (property.PropertyType.IsNullableType() && property.PropertyType.GenericTypeArguments[0].IsEnum)
            {
                propertyValue = Enum.Parse(property.PropertyType.GenericTypeArguments[0], value.ToString()!);
            }
            else if (property.PropertyType == typeof(DateTime?))
            {
                propertyValue = DateTime.Parse(value.ToString()!);
            }
            else
            {
                propertyValue = Convert.ChangeType(value, property.PropertyType);
            }
        }

        property.SetValue(obj, propertyValue);
    }

    public static bool IsNullableType(this Type type)
    {
        if (type.GetTypeInfo().IsGenericType)
            return type.GetGenericTypeDefinition() == (object)typeof(Nullable<>);
        return false;
    }

    public static bool TryGetParentTypeOfGeneric(this Type type, Type genericType, out Type? parentType)
    {
        var subType = type;

        while (subType != typeof(object))
        {
            if (!genericType.IsGenericType || subType == null)
            {
                parentType = null;
                return false;
            }

            if (subType.IsGenericType && subType.GetGenericTypeDefinition() == genericType)
            {
                parentType = subType;
                return true;
            }

            subType = subType.BaseType;

        }
        parentType = null;
        return false;
    }

    public static bool TryGetCustomAttribute<T>(this Type type, bool inherit, out T? attribute) where T : Attribute
    {
        attribute = type.GetCustomAttribute<T>(inherit);
        return attribute != null;
    }

    public static bool TryGetCustomAttribute<T>(this MemberInfo mi, bool inherit, out T? attribute) where T : Attribute
    {
        attribute = mi.GetCustomAttribute<T>(inherit);
        return attribute != null;
    }


    public static bool HasCustomAttribute<T>(this Type type, bool inherit) where T : Attribute
    {
        return type.GetCustomAttribute<T>(inherit) != null;
    }

    public static string GetFriendlyName(this Type type)
    {
        if (type.TryGetCustomAttribute(false, out DisplayNameAttribute? display))
        {
            return display!.DisplayName;
        }

        return type.Name;
    }

    /// <summary>
    /// Allows accessing private fields efficiently.
    /// </summary>
    /// <typeparam name="TOwner">Type of the field's owner.</typeparam>
    /// <typeparam name="TField">Type of the field.</typeparam>
    /// <param name="fieldName">The field name.</param>
    /// <returns>A delegate field accessor.</returns>
    public static Func<TOwner, TField> GenerateGetField<TOwner, TField>(string fieldName)
    {
        var param = Expression.Parameter(typeof(TOwner));
        return Expression.Lambda<Func<TOwner, TField>>(Expression.Field(param, fieldName), param).Compile();
    }

    public static bool HasField<T>(string field)
    {
        return typeof(T).GetRuntimeFields().Any(x => x.Name == field);
    }

    /// <summary>
    /// 在指定程序集中查找其实现类
    /// </summary>
    public static Type? GetImplementType<T>(Assembly assembly, bool isAbstract, bool isPublic)
    {
        return assembly
            .GetTypes()
            .Where(t => typeof(T).IsAssignableFrom(t))
            .Where(t => t.IsAbstract == isAbstract)
            .Where(t => t.IsPublic == isPublic)
            .FirstOrDefault(t => t.IsClass);
    }
}